/*
 * Copyright (c) 2020, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software;Designed and Developed mainly by many Chinese
 * opensource volunteers. you can redistribute it and/or modify it under the
 * terms of the GNU General Public License version 2 only, as published by the
 * Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Any questions about this component can be directed to it's project Web address
 * https://code.google.com/p/opencloudb/.
 *
 */
package io.mycat;

import io.mycat.config.MycatConfig;
import io.mycat.config.model.encrypt.EncryptColumn;
import io.mycat.config.model.encrypt.EncryptDataSource;
import io.mycat.config.model.encrypt.EncryptHelper;
import io.mycat.config.model.encrypt.EncryptServer;
import io.mycat.config.model.encrypt.EncryptTablePk;
import io.mycat.config.model.encrypt.constant.YesOrNo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author mycat
 */
public class MycatServer {

	private static final Logger LOGGER = LoggerFactory.getLogger("MycatServer");
	private static final MycatServer INSTANCE = new MycatServer();

	public static final MycatServer getInstance() {
		return INSTANCE;
	}

	private final MycatConfig config;
	/**
	 * zrx 自动加密定时器
	 */
	private final ExecutorService encryptAutoScheduler;


	private MycatServer() {
		//读取文件配置
		this.config = new MycatConfig();
		//定时加密表的字段
		encryptAutoScheduler = Executors.newSingleThreadExecutor();
	}


	public MycatConfig getConfig() {
		return config;
	}


	public void startup() {
		//zrx 启动解密
		encryptAutoScheduler.execute(decrypt());
	}

	/**
	 * zrx 解密
	 *
	 * @return
	 */
	private Runnable decrypt() {
		return () -> {
			LOGGER.info("decrypt start!");
			try {
				String driverClass = "com.mysql.cj.jdbc.Driver";
				//读取加密配置
				Map<String, EncryptServer> encryptConfigMap = MycatServer.getInstance().getConfig().getEncryptConfigMap();
				for (Map.Entry<String, EncryptServer> entry : encryptConfigMap.entrySet()) {
					EncryptServer encryptServer = entry.getValue();
					//获取server下的数据库
					Map<String, EncryptDataSource> encryptDataSourceMap = encryptServer.getEncryptDataSourceMap();
					//遍历，连接数据库
					for (Map.Entry<String, EncryptDataSource> dataSourceEntry : encryptDataSourceMap.entrySet()) {
						EncryptDataSource dataSource = dataSourceEntry.getValue();
						if (!YesOrNo.YES.getCode().toString().equals(dataSource.getAuto())) {
							continue;
						}
						if (!"mysql".equalsIgnoreCase(dataSource.getDbType())) {
							LOGGER.warn("decrypt auto only support mysql at present!");
							continue;
						}
						Connection conn = null;
						try {
							String url = "jdbc:mysql://" + dataSource.getIp() + ":" + dataSource.getPort() + "/"
									+ dataSource.getName()
									+ "?allowPublicKeyRetrieval=true&useSSL=false&connectTimeout=10000&socketTimeout=10000&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull";
							Class.forName(driverClass);
							DriverManager.setLoginTimeout(10);
							conn = DriverManager.getConnection(url, dataSource.getUsername(),
									dataSource.getPassword());
							//获取需要解密的列
							Map<String, EncryptColumn> encryptColumnMap = dataSource.getEncryptColumnMap();
							for (Map.Entry<String, EncryptColumn> columnEntry : encryptColumnMap.entrySet()) {
								EncryptColumn column = columnEntry.getValue();
								String columnName = column.getName();
								List<EncryptTablePk> encryptTablePks = column.getEncryptTablePks();
								//遍历表，解密
								for (EncryptTablePk encryptTablePk : encryptTablePks) {
									PreparedStatement pst = null;
									ResultSet rs = null;
									String table = encryptTablePk.getName();
									String pk = encryptTablePk.getPk();
									String countSql = "SELECT COUNT(1) FROM " + table + " WHERE " + columnName + " LIKE '!zl%'";
									long count;
									try {
										pst = conn.prepareStatement(countSql);
										//获取个数
										rs = pst.executeQuery();
										rs.next();
										count = rs.getLong(1);
									} finally {
										if (rs != null) {
											rs.close();
										}
										if (pst != null) {
											pst.close();
										}
									}
									//构建sql
									String baseSql = "SELECT " + columnName + "," + pk + " FROM " + table + " WHERE " + columnName + " LIKE '!zl%' order by " + pk + " LIMIT ";
									int limit = 1000;
									long page = count % limit == 0 ? count / limit : count / limit + 1;
									for (int i = 0; i <= page; i++) {
										try (PreparedStatement selpst = conn.prepareStatement(baseSql + "0," + limit); ResultSet selrs = selpst.executeQuery()) {
											while (selrs.next()) {
												PreparedStatement uppst = null;
												//获取加密后的16进制字符串
												String colVal = selrs.getString(columnName);
												Object pkVal = selrs.getObject(pk);
												try {
													String updateSql = "UPDATE " + table + " SET " + columnName + "=? WHERE " + pk + "=?";
													uppst = conn.prepareStatement(updateSql);
													//获取解密后的字节数组
													byte[] decode = EncryptHelper.decode(EncryptHelper.hexStringToBytes(colVal));
													uppst.setString(1, new String(decode, StandardCharsets.UTF_8));
													uppst.setObject(2, pkVal);
													uppst.execute();
												} finally {
													if (uppst != null) {
														uppst.close();
													}
												}
											}
										}
									}
								}
							}
						} finally {
							if (conn != null) {
								conn.close();
							}
						}
					}
				}
			} catch (Exception e) {
				LOGGER.error("decrypt error", e);
				System.exit(-1);
			}
			LOGGER.info("decrypt end!");
			System.exit(-1);
		};
	}

}
